export default class RandString {
    private readonly seeds: string;

    private constructor(seeds?: string) {
        this.seeds = seeds || this.getDefaultSeeds();
    }

    private static defaultSeeds: string;

    private getDefaultSeeds() {
        if (!RandString.defaultSeeds) {
            return RandString.defaultSeeds =
                '1234567890abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ';
        }
        return RandString.defaultSeeds;
    }

    public get(minLen: number, maxLen: number = minLen, excludeSelected: boolean = false) {
        minLen = ~~minLen;
        minLen = 0 < minLen ? minLen : 1;

        maxLen = ~~maxLen;
        maxLen = minLen < maxLen ? maxLen : minLen;

        const res: string[] = [];
        const seeds: string = this.seeds;
        const seedsCount: number = seeds.length;

        let len = minLen + ~~((1 + maxLen - minLen) * Math.random());

        if (!excludeSelected) {
            for (; len > 0; len--) {
                res.push(seeds.charAt(~~(Math.random() * seedsCount)));
            }
        } else if (len < seedsCount) {
            const selectedIndex: number[] = [];
            for (; len > 0; len--) {
                const index = ~~(Math.random() * seedsCount);
                if (-1 === selectedIndex.indexOf(index)) {
                    res.push(seeds.charAt(index));
                    selectedIndex.push(index);
                } else {
                    len++;
                }
            }
        } else {
            for (; len > seedsCount; len--) {
                res.push('0');
            }
            res.push(seeds);
        }

        return res.join('');
    }


    private static defaultInstance: RandString;

    public static getInstance(seeds?: string) {
        if (!seeds) {
            return this.defaultInstance ||
                (this.defaultInstance = new RandString());
        } else {
            return new RandString(seeds);
        }
    }

    public static get(minLen: number, maxLen: number = minLen, excludeShotten: boolean = false) {
        return this.getInstance().get(minLen, maxLen, excludeShotten);
    }
}